/*=============================================================
(c) 2012 by Juliano Efson Sales
www.mdcc.ufc.br
================================================================*/

using HPE.Port;
using HPE.Connector.Meta;
using HPE.Connector.Load;
using HPE.Connector.Run;
using HPE.Connector.Config.Reconfig;
using HPE.Connector.Config.Monitoring;

using System;
using System.Threading;
using System.Runtime.CompilerServices;
using System.Collections.Generic;

namespace HPE.Connector.Config {
   
   /*Gerenciador de configuração. É responsável pelo acompanhamento da execução (Papel do Monitor)
   e aplicação de reconfigurações dinâmicas.
   
   Tem o seu funcinamento vinculado a um WorkObject.*/
   public class ConfigurationManager : IConfigurationManager, IObserver {
      
      protected XmlLoader loader;
      
      protected int index;
      
      protected string unitName;

      protected MetaHashComponent application;

      //Unit principal a ser executada no nó.
      protected MetaUnit unit = null;
      public MetaUnit Unit {
         get {return unit;}
      }
      
      //Interpretador a conduzir a execução.
      protected List<IInterpreter> interpreters = null;
      
      //Indica se o manager está aguardando por um estado para aplicar uma reconfiguração. 
      protected bool waiting;
      
      //Semaforo para realizar a espera de um estado viável para a reconfiguração.
      protected System.Threading.ManualResetEvent resetEvent;
      
      private Object thisLock = new Object();
      
      protected bool reconfiguring;
      public bool IsReconfiguring {
         get {return reconfiguring;}
      }
      
      protected List<ExecutionStateEvaluation> evaluations = null;
      
      protected ReconfigurationRequest request = null;
      
      public ConfigurationManager(ref int index, ref string unitName) {
         this.index = index;
         this.unitName = unitName;
         
         this.waiting = this.reconfiguring = false;
         resetEvent = new System.Threading.ManualResetEvent(false);
         
         //TODO depois de finalizar a execução, tirar dessa lista.
         interpreters = new List<IInterpreter>();
         
      }
      
      public void LoadComponent(string xml) {
         this.loader = new XmlLoader();
         this.application = loader.loadComponent(xml);
         
         foreach(MetaUnit u in application.Units) {
            if(u.Name.Equals(this.unitName)) {
               this.unit = u;
               u.Index = this.index;
               break;
            }
         }
         
         if(this.unit == null) {
            throw new Exception("Unit not found. Name: " + unitName + " | Index: " + index);
         }
      }
      
      public void Run() {
         Run("go");
      }
      
      //Inicia a execução da configuração.
      [MethodImpl(MethodImplOptions.Synchronized)]
      public void Run(string actionName) {
         MetaAction action = null;
         
         if(unit != null && unit.Entity != null) {
            for(int i = 0; i < unit.Actions.Count; i++) {
               if(unit.Actions[i].Name.Equals(actionName)) {
                  action = unit.Actions[i];
                  break;
               }
            }
            
            if(action != null) {
               
               Run(action);
               
            } else {
               throw new Exception("Invalid action name.");
            }
         } else {
            
            throw new Exception("Null entity.");
         }
      }

      public void Run(MetaAction action) {
         action.Protocol.AddMonitor(new HPE.Connector.Config.Monitoring.Monitor());
         
         Interpreter interpreter = new Interpreter(action);
         interpreters.Add(interpreter);
         interpreter.Go();
      }

      public bool EvaluateReconfiguration(string xmlRequest) {
         ReconfigurationRequest r = loader.loadRequest(xmlRequest, application);

         return EvaluateReconfiguration(r);
      }

      public bool EvaluateReconfiguration(ReconfigurationRequest request) {
         lock(thisLock) {
            if (!reconfiguring) {
               
               this.reconfiguring = true;            
               this.evaluations = SecurityAnalyzer.Evaluate(request, unit);
               
               if(SecurityAnalyzer.isDeadlockFree(evaluations, unit)) {
                  this.request = request;
                  return true;
                  
               } else {
                  this.evaluations = null;
                  this.reconfiguring = false;
                  return false;
               }
               
            } else {
               throw new Exception("Já existe uma reconfiguração em adamento. " +
                                   " Esta deverá ser confirmada ou cancelada. Antes de nova submissão.");
            }
         }
      }
      
      //TODO MEDIO indexar as actions pelo nome;
      public bool CommitReconfiguration() {
         Configuration defaultProtocol;
         
         lock(thisLock) {
            if(reconfiguring) {
               
               foreach(ExecutionStateEvaluation es in evaluations) {
                  foreach(MetaAction action in unit.Actions) {
                     
                     if(!action.IsNative && action.Name.Equals(es.ActionName)) {
                        defaultProtocol = action.Protocol;
                        
                        defaultProtocol.stopStates(es.StatesToStop);
                        WaitForSafeState(defaultProtocol.ReconfigMonitor, es);
                        
                        foreach(StructuralChange sc in request.StructuralRequest.Changes) {
                           //reconManager.SubstituteUnit(sc.Old, sc.New);
                        }
                        
                        foreach(BehavioralReconfigurationRequest brr in request.BehavioralRequests) {
                           foreach(BehavioralChange bc in brr.Changes) {
                              
                         //     if(bc.Slices != null && bc.Slices.Count > 0) {
                              //recon.AddSlices(bc.Slices);   
                         //     }
                              
                              //TODO ALTA alterar o com as transations defaultProtocol();
                           }
                        }
                        
                        break;
                     }
                     
                  }
               }            
               
               foreach(ExecutionStateEvaluation es in evaluations) {
                  foreach(MetaAction action in unit.Actions) {
                     
                     if(!action.IsNative && action.Name.Equals(es.ActionName)) {
                        action.Protocol.runStates(es.StatesToStop);
                     }
                  }
               }
               
               this.reconfiguring = false;
               this.evaluations = null;
               this.request = null;
               return true;
            }
         }
         return false;
      }
      
      //TODO usar um locker para os três métodos. Cancel, Commit, Evaluate.
      public void CancelReconfiguration() {
         lock(thisLock) {         
            this.reconfiguring = false;
            this.evaluations = null;
            this.request = null;
         }
      }
      //Aguarda um estado seguro onde seja possível realizar a reconfiguração.
      //Os estados seguros são identificados pelo es.
      protected void WaitForSafeState(IMonitor monitor, ExecutionStateEvaluation es) {
         
         //System.Console.WriteLine(es.ToString());
         if(es.CriticalActions != null) {
            waiting = monitor.isRunning(es.CriticalActions);
            while(waiting) {
               
               //TODO BAIXO: resolver essa marmota de ter que criar um array.
               System.Threading.WaitHandle.WaitAll(new System.Threading.ManualResetEvent[] {resetEvent});
               waiting = monitor.isRunning(es.CriticalActions); 
            }
         }
         
         if(es.CriticalIntervals != null) {
            foreach(Interval interval in es.CriticalIntervals) {
               
               waiting = monitor.isRunning(interval.InitialState, interval.FinalState);
               while(waiting) {
                  
                  System.Threading.WaitHandle.WaitAll(new System.Threading.ManualResetEvent[] {resetEvent});
                  waiting = monitor.isRunning(interval.InitialState, interval.FinalState);
               }
            }
         }
      }
      
      //Método para que o monitor possa indicar que algum estado se concluio. É chamado apenas durante
      //o tempo de espera por um estado seguro.
      public void Notify() {
         if(waiting) {
            resetEvent.Set();
         }
      }

      public void PersistConfiguration() {
         
      }
   }
}
